// This is the main DLL file.

#include "stdafx.h"
#include <vcclr.h>
#include <functional>

#include "HttpListener.h"

#include "HttpContext.h"
#include "HttpRequest.h"
#include "HttpResponse.h"

#include "poco/Event.h"
#include "poco/Net/HTTPServer.h"
#include "poco/Net/HTTPServerParams.h"
#include "poco/Net/ServerSocket.h"
#include "poco/Net/HTTPRequestHandlerFactory.h"
#include "poco/Net/HTTPRequestHandler.h"
#include "poco/Net/HTTPServerRequest.h"
#include "poco/Net/HTTPServerResponse.h"

#include "StreamWrappers.h"

namespace MiniHttpServer
{
	typedef std::function<void (Poco::Net::HTTPServerRequest&, Poco::Net::HTTPServerResponse&, std::shared_ptr<HttpContextHandler> const&)> RequestHandlerMethod;

	class HttpRequestHandler : public Poco::Net::HTTPRequestHandler
	{
	public:
		HttpRequestHandler(RequestHandlerMethod const& funcRequestHandler)
			: m_funcRequestHandler(funcRequestHandler)
		{

		}
		virtual void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response)
		{
			response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_OK);
			Poco::Event eventRequestHandled;
			if (m_funcRequestHandler)
				m_funcRequestHandler(request, response, std::make_shared<HttpContextHandler>(eventRequestHandled));

			// Now this is a weird construction. NCNetwork as well as NCCore like to handle http requests
			// completly asynchronously. It is not possible currently that they handle http requests directly
			// within this callback. So we have to wait until NCNetwork/NCCore tell us that the request has been handled
			// only then we return to the caller. Otherwise, the request and response objects would be rendered
			// invalid (unfortunately, this is not done via reference counting here, which would be far nicer).
			eventRequestHandled.wait();
			if (!response.sent())
				response.sendBuffer(nullptr, 0);
		}
	private:
		RequestHandlerMethod m_funcRequestHandler;
	};

	class HttpRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory
	{
	public:
		HttpRequestHandlerFactory(RequestHandlerMethod const& funcRequestHandler)
			: m_funcRequestHandler(funcRequestHandler)
		{

		}

		virtual Poco::Net::HTTPRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request)
		{
			// usually we should dispatch the request here already according to the requested service...
			// well, won't work like this, the dispatching is done later, so we forget the request again here
			return new HttpRequestHandler(m_funcRequestHandler);
		}
	private:
		RequestHandlerMethod m_funcRequestHandler;
	};

	HttpListener::HttpListener(System::Net::IPAddress^ ipAddress, int nPort )
		: m_ipAddress(ipAddress)
		, m_nPort(nPort)
	{
	}

	HttpListener::~HttpListener()
	{
		this->!HttpListener();
	}

	HttpListener::!HttpListener()
	{
		Stop();
	}

	HttpListener^ HttpListener::Create(System::Net::IPAddress^ ipAddress, int nPort )
	{
		return gcnew HttpListener(ipAddress, nPort);
	}

	// unfortunately, currently I have no other idea how to solve this, std::bind does not accept methods of managed classes
	void HttpListener::DelegateCaller(gcroot<HttpListener::RequestHandlerDelegate^> const& caller, Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response, std::shared_ptr<HttpContextHandler> const& pContext)
	{
		caller->Invoke(request, response, pContext);
	}

	void HttpListener::Start()
	{
		array<unsigned char>^ arrayIpAddress = System::Text::Encoding::UTF8->GetBytes(m_ipAddress->ToString());
		pin_ptr<unsigned char> pIpAddressString = &arrayIpAddress[0];
		m_httpServer = new Poco::Net::HTTPServer(
			new HttpRequestHandlerFactory(
				// unfortunately, currently I have no other idea how to solve this, std::bind does not accept methods of managed classes
				std::bind(
					&DelegateCaller, 
					gcroot<RequestHandlerDelegate^>(
						gcnew RequestHandlerDelegate(
							this, 
							&HttpListener::RequestHandler
						)
					), 
					std::placeholders::_1, 
					std::placeholders::_2,
					std::placeholders::_3
				)
			), 
			Poco::Net::ServerSocket(
				Poco::Net::SocketAddress(
				Poco::Net::IPAddress(
						reinterpret_cast<char*>(static_cast<unsigned char*>(pIpAddressString))
					),
					static_cast<Poco::UInt16>(m_nPort)
				)
			),
			new Poco::Net::HTTPServerParams()
		);
		m_httpServer->start();
	}

	void HttpListener::Stop()
	{
		if (m_httpServer)
		{
			m_httpServer->stop();
			delete m_httpServer;
			m_httpServer = nullptr;
		}
	}


	void HttpListener::RequestHandler( Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response, std::shared_ptr<HttpContextHandler> const& pContext)
	{
		if (m_requestReceiver)
			m_requestReceiver(gcnew HttpContext(request, response, pContext));
	}

	void HttpListener::SetRequestReceiver( RequestReceiverDelegate^ receiver )
	{
		m_requestReceiver = receiver;
	}

	IHttpListener^ IHttpListener::Create(System::Net::IPAddress^ ipAddress, int nPort)
	{
		return HttpListener::Create(ipAddress, nPort);
	}

}
